home *** CD-ROM | disk | FTP | other *** search
Wrap
Text File | 1992-04-25 | 36.0 KB | 1,262 lines
/* DrawDocument.m by Paul Hegarty, NeXT, Inc. * Delegate to drawing Window. * Modified to support HippoDraw * * Copyright (C) 1991 The Board of Trustees of * The Leland Stanford Junior University. All Rights Reserved. */ #import "DrawDocument.h" const char DrawDocument_h_rcsid[] = DRAWDOCUMENT_H_ID; const char DrawDocument_m_rcsid[]= "$Id: DrawDocument.m,v 1.38 1992/04/13 20:43:55 pfkeb Rel $"; #import "HDraw.h" #import "HGraphicView.h" #import "DrawPageLayout.h" #import "GridView.h" #import "InspectTuple.h" #import "SyncScrollView.h" #import "Ruler.h" #import "hippo.h" #import <appkit/Application.h> #import <appkit/Cursor.h> #import <appkit/Listener.h> #import <appkit/Matrix.h> #import <appkit/Pasteboard.h> #import <appkit/PrintInfo.h> #import <appkit/SavePanel.h> #import <appkit/Speaker.h> #import <appkit/nextstd.h> #import <appkit/publicWraps.h> #import <objc/List.h> #import <objc/hashtable.h> /* for NXCopyStringBuffer() */ #import <zone.h> #import <string.h> #import <libc.h> #import <mach.h> #import <sys/types.h> #define Notify(title, msg) NXRunAlertPanel(title, msg, "OK", NULL, NULL) #define NEW_DRAW_VERSION 184 @implementation DrawDocument /* * This class is used to keep track of a Draw document. * * Its view and window instance variables keep track of the GraphicView * comprising the document as well as the window it is in. * The printInfo instance variable is used to allow the user to control * how the printed page is printed. It is an instance of a PrintInfo * object (which is edited via the PageLayout and PrintPanels). * The listener is used to allow the user to drag an icon representing * a PostScript or TIFF file into the document. The iconPathList is the * list of files which was last dragged into the document. * The name and directory specify where the document is to be saved. * haveSavedDocument keeps track of whether a disk file is associated * with the document yet (i.e. if it has ever been saved). * * The DrawDocument class's responsibilities: * * 1. Manage the window (including the scrolling view) which holds the * document's GraphicView. This includes constraining the resizing of * the window so that it never becomes larger than the GraphicView, and * ensuring that if the window contains an unsaved document and the user * tries to close it, the user gets an opportunity to save her changes. * 2. Handle communication with the Workspace Manager which allows icons * for PostScript and TIFF files to be dragged into the document window * and be assimilated into the document. * 3. Saving the document to a disk file. * 4. Provide an external interface to saving the contents of the GraphicView * as a PostScript or TIFF file. */ #define MIN_WINDOW_WIDTH 50.0 #define MIN_WINDOW_HEIGHT 75.0 #define SCROLLVIEW_BORDER NX_NOBORDER static NXRect *calcFrame(id printInfo, NXRect *viewRect) /* * Calculates the size of the page the user has chosen minus its margins. */ { float lm, rm, bm, tm; const NXRect *paperRect; viewRect->origin.x = viewRect->origin.y = 0.0; paperRect = [printInfo paperRect]; [printInfo getMarginLeft:&lm right:&rm top:&tm bottom:&bm]; viewRect->size = paperRect->size; viewRect->size.width -= lm + rm; viewRect->size.height -= tm + bm; return viewRect; } static void getContentSizeForView(id view, NXSize *contentSize) /* * Calculates the size of the window's contentView by accounting for the * existence of the ScrollView around the GraphicView. No scrollers are * assumed since we are interested in the minimum size need to enclose * the entire view and, if the entire view is visible, we don't need * scroll bars! */ { NXRect viewFrame; [view getFrame:&viewFrame]; [SyncScrollView getFrameSize:contentSize forContentSize:&viewFrame.size horizScroller:YES vertScroller:YES borderType:SCROLLVIEW_BORDER]; } #define WINDOW_MASK (NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK|NX_FLAGSCHANGEDMASK) static id createWindowFor(id view, NXRect *windowContentRect) /* * Creates a window for the specified view. * If windowContentRect is NULL, then a window big enough to fit the whole * view is created (unless that would be too big to comfortably fit on the * screen, in which case a smaller window may be allocated). * If windowContentRect is not NULL, then it is used as the contentView of * the newly created window. * * setMiniwindowIcon: sets the name of the bitmap which will be used in * the miniwindow of the window (i.e. when the window is miniaturized). * The icon "drawdoc" was defined in InterfaceBuilder (take a look in * the icon suitcase). */ { NXSize screenSize; id scrollView, window; NXRect defaultWindowContentRect; if (!windowContentRect) { windowContentRect = &defaultWindowContentRect; getContentSizeForView(view, &windowContentRect->size); [NXApp getScreenSize:&screenSize]; if (windowContentRect->size.width > screenSize.width / 2.0) { windowContentRect->size.width = floor(screenSize.width / 2.0); } if (windowContentRect->size.height > screenSize.height - 300.0) { windowContentRect->size.height = screenSize.height - 300.0; } windowContentRect->origin.x = 200.0; /*screenSize.width - 75.0 - windowContentRect->size.width;*/ windowContentRect->origin.y = floor(screenSize.height - windowContentRect->size.height-24.0); } window = [[Window allocFromZone:[view zone]] initContent:windowContentRect style:NX_TITLEDSTYLE backing:(InMsgPrint ? NX_NONRETAINED : NX_BUFFERED) buttonMask:NX_ALLBUTTONS defer:(InMsgPrint ? NO : YES)]; scrollView = [[SyncScrollView allocFromZone:[view zone]] initFrame:windowContentRect]; [scrollView setRulerClass:[Ruler class]]; [scrollView setRulerOrigin:UpperLeft]; [scrollView setRulerWidths:[Ruler width] :[Ruler width]]; [scrollView setVertScrollerRequired:YES]; [scrollView setHorizScrollerRequired:YES]; [scrollView setBorderType:SCROLLVIEW_BORDER]; [scrollView setDocView:view]; [window setContentView:scrollView]; [window addToEventMask:WINDOW_MASK]; [window makeFirstResponder:view]; [window setMiniwindowIcon:"hippo"]; return window; } /* Very private methods needed by factory methods */ + backupOldDrawDocument:(const char *)file /* * We do this because we are still beta-testing the new version. */ { int version; volatile BOOL isOld = NO; NXStream *volatile stream; char *extension, *dash; NXTypedStream *volatile ts = NULL; char buffer[MAXPATHLEN+1]; NX_DURING stream = NXMapFile(file, NX_READONLY); if (stream) ts = NXOpenTypedStream(stream, NX_READONLY); if (ts) { NXReadType(ts, "i", &version); if (version != NEW_DRAW_VERSION) isOld = YES; } NX_HANDLER NX_ENDHANDLER if (ts) NXCloseTypedStream(ts); if (stream) NXClose(stream); if (isOld) { strcpy(buffer, file); extension = strrchr(buffer, '.'); if (extension && !strcmp(extension, ".hdraw")) { dash = strrchr(buffer, '-'); if (!dash || strcmp(dash, "-1.0.hdraw")) strcpy(extension, "-1.0.hdraw"); } else { strcat(buffer, "-1.0"); } link(file, buffer); } return self; } - (BOOL)loadDocument:(NXStream *)stream frameSize:(NXRect *)frame /* * Loads an archived document from the specified filename. * Loads the window frame specified in the archived document into the * frame argument (if the frame argument is NULL, then the frame in * the archived document is ignored). Returns YES if the document * has been successfully loaded, NO otherwise. Note that this method * destroys the receiving document, so use with extreme care * (essentially, this should only be called when a new document is * being created or an existing one is being reverted to its form * on disk). * * An NX_DURING handler is needed around the NXTypedStream operations because * if the user has asked that a bogus file be opened, the NXTypedStream will * raise an error. To handle the error, the NXTypedStream must be closed. */ { int version; NXRect dummyRect; volatile BOOL retval = YES; NXTypedStream *volatile ts = NULL; if (!frame) frame = &dummyRect; NX_DURING ts = NXOpenTypedStream(stream, NX_READONLY); if (ts) { NXSetTypedStreamZone(ts, [self zone]); NXReadType(ts, "i", &version); /* historical */ printInfo = NXReadObject(ts); NXReadRect(ts, frame); view = NXReadObject(ts); } else { retval = NO; } NX_HANDLER retval = NO; NX_ENDHANDLER if (ts) NXCloseTypedStream(ts); return retval; } /* Factory methods */ /* * We reuse zones since it doesn't cost us anything to have a * zone lying around (e.g. if we open ten documents at the start * then don't use 8 of them for the rest of the session, it doesn't * cost us anything except VM (no real memory cost)), and it is * risky business to go around NXDestroy()'ing zones since if * your application accidentally allocates some piece of global * data into a zone that gets destroyed, you could have a pointer * to freed data on your hands! We use the List object since it * is so easy to use (which is okay as long as 'id' remains a * pointer just like (NXZone *) is a pointer!). * * Note that we don't implement alloc and allocFromZone: because * we create our own zone to put ourselves in. It is generally a * good idea to "notImplemented:" those methods if you do not allow * an object to be alloc'ed from an arbitrary zone (other examples * include Application and all of the Application Kit panels * (which allocate themselves into their own zone). */ static id zoneList = nil; + (NXZone *)newZone { if (!zoneList || ![zoneList count]) { return NXCreateZone(vm_page_size, vm_page_size, YES); } else { return (NXZone *)[zoneList removeLastObject]; } } + (void)reuseZone:(NXZone *)aZone { if (!zoneList) zoneList = [List new]; [zoneList addObject:(id)aZone]; NXNameZone(aZone, "Unused"); } + allocFromZone:(NXZone *)aZone { return [self notImplemented:@selector(allocFromZone:)]; } + alloc { return [self notImplemented:@selector(alloc)]; } /* Creation methods */ + new /* * Creates a new, empty, document. * * Creates a PrintInfo object; creates a view whose size depends on the * default PrintInfo created; creates a window for that view; sets self * as the window's delegate; orders the window front; registers the window * with the Workspace Manager. Note that the default margins are set * to 1/2 inch--that's more appropriate for a draw program than 1 or 1.25 * inches. */ { NXZone *zone; NXRect frameRect; NXPoint top; zone = [self newZone]; self = [super allocFromZone:zone]; [self registerForServicesMenu]; printInfo = [PrintInfo new]; [printInfo setMarginLeft:36.0 right:36.0 top:36.0 bottom:36.0]; calcFrame(printInfo, &frameRect); view = [[HGraphicView allocFromZone:zone] initFrame:&frameRect]; [view setClipping:NO]; /* since it is in a ClipView */ [view setGridSpacing:10]; [view setGridVisible:YES]; window = createWindowFor(view, NULL); [window setDelegate:self]; top.x = 0; top.y = frameRect.size.height; [view scrollPoint:&top]; [self resetScrollers]; [self setName:NULL andDirectory:NULL]; [window makeKeyAndOrderFront:self]; [self registerWindow]; return self; } + newFromStream:(NXStream *)stream /* * Creates a new document from what is in the passed stream. */ { NXZone *zone; NXRect contentViewFrame, frameRect; NXPoint top; zone = [self newZone]; self = [super allocFromZone:zone]; [self registerForServicesMenu]; if (stream && [self loadDocument:stream frameSize:&contentViewFrame]) { window = createWindowFor(view, &contentViewFrame); [window setDelegate:self]; [view getFrame:&frameRect]; top.x = 0; top.y = frameRect.size.height; [view scrollPoint:&top]; [self resetScrollers]; if (!InMsgPrint) { [window makeKeyAndOrderFront:self]; [self registerWindow]; } haveSavedDocument = YES; return self; } else { Notify("Open Draw Document", "I/O error. Can't open file."); [self free]; return nil; } } + newFromFile:(const char *)file /* * Opens an existing document from the specified file. */ { NXStream *stream; [self backupOldDrawDocument:file]; stream = NXMapFile(file, NX_READONLY); if (stream) { if (self = [self newFromStream:stream]) [self setName:file]; NXClose(stream); return self; } else { return nil; } } - openTupleFile:(const char *)path { id inspectTuple; BOOL refFlag; int irc; if ( hDraw == nil ) { hDraw = NXGetNamedObject("HDrawInstance", NXApp); } inspectTuple = [hDraw inspectTuple]; irc = NXRunAlertPanel( "Open", "Import tuple by reference?", "Yes", "No", NULL); refFlag = (irc == NX_ALERTDEFAULT) ? YES : NO ; irc = [inspectTuple openTupleFile:path by:refFlag]; if ( irc == HD_CANCEL ) { return self; } if ( irc == HD_REPLACE ) { [inspectTuple bindDisplays]; } [view reDrawPlot]; return self; } - free { [printInfo free]; [window free]; NX_FREE(name); NX_FREE(directory); NX_FREE(iconPathList); [[self class] reuseZone:[self zone]]; return [super free]; } /* Services menu support methods. */ /* Services menu registrar */ - registerForServicesMenu { static BOOL registered = NO; const char *validSendTypes[2]; if (!registered) { registered = YES; validSendTypes[0] = NXFilenamePboardType; validSendTypes[1] = NULL; [NXApp registerServicesMenuSendTypes:validSendTypes andReturnTypes:NULL]; } return self; } - validRequestorForSendType:(NXAtom)sendType andReturnType:(NXAtom)returnType /* * Services menu support. * We are a valid requestor if the send type is filename * and there is no return data from the request. */ { return (haveSavedDocument && sendType == NXFilenamePboardType && (!returnType || !*returnType)) ? self : nil; } - writeSelectionToPasteboard:pboard types:(NXAtom *)types /* * Services menu support. * Here we are asked by the Services menu mechanism to supply * the filename (which we said we were a valid requestor for * in the above method). */ { int save; if (haveSavedDocument) { while (types && *types) if (*types == NXFilenamePboardType) break; else types++; if (types && *types) { if ([view isDirty]) { save = NXRunAlertPanel("Service", "Do you wish to save this document before your request is serviced?", "Save", "Don't Save", NULL); if (save == NX_ALERTDEFAULT) [self save]; } [pboard declareTypes:&NXFilenamePboardType num:1 owner:self]; [pboard writeType:NXFilenamePboardType data:[self filename] length:strlen([self filename])+1]; return self; } } return nil; } /* Other methods. */ - loadImageFile:(const char *)file at:(const NXPoint *)p allowAlpha:(BOOL)alphaOk /* * Maps in the specified file and asks the view to load the PostScript in * from the resulting NXStream. The PostScript image will be centered at * the point p (in the GraphicView's coordinate system). This is called * from the icon-dragging mechanism (icons dragged from the Workspace into * the document--see registerWindow). */ { NXStream *stream = NXMapFile(file, NX_READONLY); [view loadImageFromStream:stream at:p allowAlpha:alphaOk]; NXClose(stream); return self; } - resetScrollers /* * Checks to see if the new window size is too large. * Called whenever the page layout (either by user action or * by the opening or reverting of a file) is changed or * the user resizes the window. */ { id scrollView; NXSize contentSize; NXRect contentRect, windowFrame; if (window) { [window getFrame:&windowFrame]; [[window class] getContentRect:&contentRect forFrameRect:&windowFrame style:[window style]]; scrollView = [window contentView]; getContentSizeForView(view, &contentSize); if ([scrollView horizontalRulerIsVisible]) contentSize.height += [Ruler width]; if ([scrollView verticalRulerIsVisible]) contentSize.width += [Ruler width]; if (contentRect.size.width >= contentSize.width || contentRect.size.height >= contentSize.height) { contentSize.width = MIN(contentRect.size.width, contentSize.width); contentSize.height = MIN(contentRect.size.height, contentSize.height); [window sizeWindow:contentSize.width :contentSize.height]; } } return self; }- view /* * Returns the GraphicView associated with this document. */ { return view; } - printInfo { return printInfo; } /* Target/Action methods */ - changeLayout:sender /* * Puts up a PageLayout panel and allows the user to pick a different * size paper to work on. After she does so, the view is resized to the * new paper size. * Since the PrintInfo is effectively part of the document, we dirty * the view (by performing the dirty method). */ { NXRect frame; if ( hDraw == nil ) { hDraw = NXGetNamedObject("HDrawInstance", NXApp); } if ([[hDraw pageLayout] runModal] == NX_OKTAG) { calcFrame(printInfo, &frame); [view sizeTo:frame.size.width :frame.size.height]; [self resetScrollers]; [view display]; [view dirty]; } return self; } - getPageFrame:(NXRect *)frame { calcFrame(printInfo, frame); return self; } - changeGrid:sender /* * Changes the grid by putting up a modal panel asking the user what * she wants the grid to look like. */ { if ( hDraw == nil ) { hDraw = NXGetNamedObject("HDrawInstance", NXApp); } [[hDraw gridInspector] runModalForGraphicView:view]; return self; } - close:sender { [window performClose:self]; return self; } - save:sender /* * Saves the file. If this document has never been saved to disk, * then a SavePanel is put up to ask the user what file name she * wishes to use to save the document. */ { id savepanel; if (!haveSavedDocument) { if ( hDraw == nil ) { hDraw = NXGetNamedObject("HDrawInstance", NXApp); } savepanel = [hDraw saveAsPanel]; if ([savepanel runModalForDirectory:directory file:name]) { [self setName:[savepanel filename]]; } else { return self; } } [self save]; return self; } - saveAs:sender { [view dirty]; haveSavedDocument = NO; return [self save:sender]; } - changeSaveType:sender /* * Called by the SavePanel accessory view whenever the user chooses * a different type of file to save to. The window of the sender * is, of course, the SavePanel itself. setRequiredFileType: does * not affect the SavePanel while it is running. It only has effect * when the user has chosen a file, and the SavePanel ensures that it * has the correct extension by adding it if it doesn't have it already. * This message gets here via the Responder chain from the SavePanel. */ { switch ([sender selectedRow]) { case 0: [[sender window] setRequiredFileType:"hdraw"]; break; case 1: [[sender window] setRequiredFileType:"eps"]; break; case 2: [[sender window] setRequiredFileType:"tiff"]; break; case 3: [[sender window] setRequiredFileType:"hippo"]; break; } return self; } - saveTo:sender /* * This takes the document and saves it as a Draw document file, PostScript * file, or TIFF file. If the document type chosen is Draw document, then * this saves the file, but DOES NOT make that file the currently edited * file (this makes it easy to save your document elsewhere as a backup * and keep on going in the current document). * * If PostScript or TIFF is selected, then the document is written out * in the appropriate format. In the case of PostScript and TIFF, the * actual saving is done using the more general method saveAs:using:. */ { id savepanel; const char *file; char *type, *savedName, *savedDirectory; BOOL viewIsDirty, reallyHaveSavedDocument; char buffer[MAXPATHLEN+1]; strcpy(buffer, name); type = strrchr(buffer, '.'); if (type) *type = '\0'; if ( hDraw == nil ) { hDraw = NXGetNamedObject("HDrawInstance", NXApp); } savepanel = [hDraw saveToPanel]; if (![savepanel runModalForDirectory:directory file:buffer]) return self; file = [savepanel filename]; type = strrchr(file, '.'); if (type) { if (!strcmp(type, ".eps")) { [self saveTo:file using:@selector(writePSToStream:)]; } else if (!strcmp(type, ".tiff")) { [self saveTo:file using:@selector(writeTIFFToStream:)]; } else if (!strcmp(type, ".hdraw")) { viewIsDirty = [view isDirty]; /* is it really dirty? */ [view dirty]; /* temporarily dirty it */ reallyHaveSavedDocument = haveSavedDocument; savedName = name; /* save current name */ savedDirectory = directory; /* save current directory */ name = NULL; directory = NULL; /* clear current filename */ [self setName:file]; /* temporarily change name */ [self save]; /* save, then restore name */ [self setName:savedName andDirectory:savedDirectory]; if (viewIsDirty) [view dirty]; /* restore correct dirtiness */ haveSavedDocument = reallyHaveSavedDocument; } else if ( !strcmp(type, ".hippo") ) { [view saveAsExportFile:file]; } } return self; } - revertToSaved:sender /* * Revert the document back to what is on the disk. */ { NXStream *stream; NXRect viewFrame, visibleRect; if (!haveSavedDocument || ![view isDirty] || (NXRunAlertPanel("Revert", "%s has been edited. Are you sure you want to undo changes?", "Revert", "Cancel", NULL, name) != NX_ALERTDEFAULT)) { return self; } [view getVisibleRect:&visibleRect]; [window endEditingFor:self]; stream = NXMapFile([self filename], NX_READONLY); if (stream && [self loadDocument:stream frameSize:NULL]) { [[[window contentView] setDocView:view] free]; calcFrame(printInfo, &viewFrame); [view sizeTo:viewFrame.size.width :viewFrame.size.height]; [self resetScrollers]; [view scrollRectToVisible:&visibleRect]; [view display]; [window reenableFlushWindow]; [window flushWindow]; [window makeFirstResponder:view]; [window setDocEdited:NO]; NXClose(stream); } else { if (stream) NXClose(stream); Notify("Revert", "I/O error. Can't revert."); } return self; } - showTextRuler:sender /* * Sent to cause the Text object ruler to be displayed. * Only does anything if the rulers are already visible. * This doesn't currently work due to an AppKit bug. */ { id scrollView = [window contentView]; if ([scrollView verticalRulerIsVisible] && [scrollView horizontalRulerIsVisible]) { [scrollView showHorizontalRuler:NO]; [sender toggleRuler:sender]; } return self; } - hideRuler:sender /* * If sender is nil, we assume the sender wants the * ruler hidden, otherwise, we toggle the ruler. * If sender is the field editor itself, we do nothing * (this allows the field editor to demand that the * ruler stay up). * This doesn't currently work properly when editing text * due to an AppKit bug. */ { id scrollView = [window contentView]; id fe = [window getFieldEditor:NO for:NXApp]; if (!sender && [scrollView verticalRulerIsVisible]) { [fe toggleRuler:sender]; [window disableDisplay]; [scrollView toggleRuler:nil]; if ([scrollView verticalRulerIsVisible]) [scrollView showHorizontalRuler:YES]; [window reenableDisplay]; [scrollView resizeSubviews:(NXSize *)0]; } else if (sender) { [fe toggleRuler:sender]; if ([scrollView verticalRulerIsVisible]) { [scrollView showVerticalRuler:NO]; [scrollView showHorizontalRuler:NO]; if (![fe window]) [scrollView toggleRuler:nil]; } else { [scrollView showVerticalRuler:YES]; if ([fe window]) { [scrollView showHorizontalRuler:NO]; } else { [scrollView toggleRuler:nil]; [scrollView showHorizontalRuler:YES]; } } } return self; } /* Methods related to naming/saving this document. */ - (const char *)filename /* * Gets the fully specified file name of the document. * If directory is NULL, then the currentDirectory is used. * If name is NULL, then the default title is used. */ { static char filenamebuf[MAXPATHLEN+1]; if (!directory && !name) { [self setName:NULL andDirectory:NULL]; } if (directory) { strcpy(filenamebuf, directory); strcat(filenamebuf, "/"); } else { filenamebuf[0] = '\0'; } if (name) { strcat(filenamebuf, name); } return filenamebuf; } - (const char *)directory { return directory; } - (const char *)name { return name; } - setName:(const char *)newName andDirectory:(const char *)newDirectory /* * Updates the name and directory of the document. * newName or newDirectory can be NULL, in which case the name or directory * will not be changed (unless one is currently not set, in which case * a default name will be used). */ { char oldName[MAXPATHLEN+1]; if ( hDraw == nil ) { hDraw = NXGetNamedObject("HDrawInstance", NXApp); } if (directory && name) { strcpy(oldName, [self filename]); } else { oldName[0] = '\0'; } if ((newName && *newName) || !name) { if (!newName || !*newName) newName = "UNTITLED"; NX_FREE(name); name = NXCopyStringBufferFromZone(newName, [self zone]); } if ((newDirectory && (*newDirectory == '/')) || !directory) { if (!newDirectory || (*newDirectory != '/')) { newDirectory = [hDraw currentDirectory]; } NX_FREE(directory); directory = NXCopyStringBufferFromZone(newDirectory, [self zone]); } [window setTitleAsFilename:[self filename]]; NXNameZone([self zone], [self filename]); return self; } - setName:(const char *)file /* * If file is a full path name, then both the name and directory of the * document is updated appropriately, otherwise, only the name is changed. */ { char *lastComponent; char path[MAXPATHLEN+1]; if (file) { strcpy(path, file); lastComponent = strrchr(path, '/'); if (lastComponent) { *lastComponent++ = '\0'; return [self setName:lastComponent andDirectory:path]; } else { return [self setName:file andDirectory:NULL]; } } return self; } - saveTo:(const char *)file using:(SEL)streamWriter /* * Performed by the saveTo: method, this method uses the streamWriter method * to have the GraphicView write itself in some foreign format (i.e., not * in Draw archive format). It does some work to make the default name * of the file being saved to be the name of the document with the appropriate * extension. It brings up the SavePanel so the user can specify the name * of the file to save to. */ { NXStream *stream; if (!file || !streamWriter) return self; stream = NXOpenMemory(NULL, 0, NX_WRITEONLY); if (stream) { [view perform:streamWriter with:(id)stream]; NXSaveToFile(stream, file); NXCloseMemory(stream, NX_FREEBUFFER); } return self; } - save /* * Writes out the document in three steps: * * 1. Write the printInfo object * 2. Write the rectangle of the contentView of the window * 3. Write the GraphicView itself. * * See GraphicView's write: method for more details on how the GraphicView * is archived. */ { id list; int version; NXTypedStream *ts; const char *saveFile = [self filename]; NXRect windowFrame, contentViewRect; char buffer[MAXPATHLEN+1]; if ([view isDirty]) { strcpy(buffer, saveFile); strcat(buffer, "~"); unlink(buffer); link(saveFile, buffer); unlink(saveFile); list = [view plotList]; [list makeObjectsPerform:@selector(changeRefFileNameIfValid:) with:(id)saveFile]; ts = NXOpenTypedStreamForFile(saveFile, NX_WRITEONLY); if (ts) { haveSavedDocument = YES; version = NEW_DRAW_VERSION; NXWriteType(ts, "i", &version); NXWriteRootObject(ts, printInfo); [window getFrame:&windowFrame]; [[window class] getContentRect:&contentViewRect forFrameRect:&windowFrame style:[window style]]; NXWriteRect(ts, &contentViewRect); NXWriteRootObject(ts, view); NXCloseTypedStream(ts); } else { Notify("Save", "Can't create file."); } } return self; } - (BOOL)needsSaving { return ([view isDirty] && (haveSavedDocument || ![view isEmpty])); } /* Window delegate methods. */ - windowWillClose:sender action:(const char *)action /* * If the GraphicView has been edited, then this asks the user if she * wants to save the changes before closing the window. When the window * is closed, the DrawDocument itself must be freed. This is accomplished * via Application's delayedFree: mechanism. Unfortunately, by the time * delayedFree: frees the DrawDocument, the window and view instance variables * will already have automatically been freed by virtue of the window's being * closed. Thus, those instance variables must be set to nil to avoid their * being freed twice. * * Returning nil from this method informs the caller that the window should * NOT be closed. Anything else implies it should be closed. */ { int save; if ([self needsSaving]) { save = NXRunAlertPanel(action, "%s has changes. Save them?", "Save", "Don't Save", "Cancel", name); if (save != NX_ALERTDEFAULT && save != NX_ALERTALTERNATE) { return nil; } else { [sender endEditingFor:self]; /* terminate any editing */ if (save == NX_ALERTDEFAULT) { [self save:nil]; } } } [self unregisterWindow]; window = nil; view = nil; [NXApp setPrintInfo:nil]; [NXApp delayedFree:self]; return self; } - windowWillClose:sender { return [self windowWillClose:sender action:"Close"]; } - windowDidBecomeMain:sender /* * Switch the Application's PrintInfo to the document's when the document * window becomes the main window. Also set the cursor appropriately * depending on which tool is currently selected. */ { [NXApp setPrintInfo:printInfo]; [self resetCursor]; return self; } - windowWillResize:sender toSize:(NXSize *)size /* * Constrains the size of the window to never be larger than the * GraphicView inside it (including the ScrollView around it). */ { NXRect fRect, cRect; getContentSizeForView(view, &cRect.size); [[window class] getFrameRect:&fRect forContentRect:&cRect style:[window style]]; if ([[window contentView] horizontalRulerIsVisible]) fRect.size.height += [Ruler width]; if ([[window contentView] verticalRulerIsVisible]) fRect.size.width += [Ruler width]; size->width = MIN(fRect.size.width, size->width); size->height = MIN(fRect.size.height, size->height); size->width = MAX(MIN_WINDOW_WIDTH, size->width); size->height = MAX(MIN_WINDOW_HEIGHT, size->height); return self; } - windowWillMiniaturize:sender toMiniwindow:counterpart { char *dot; char title[MAXPATHLEN+1]; strcpy(title, [self name]); dot = strrchr(title, '.'); if (dot && !strcmp(dot, ".hdraw")) *dot = '\0'; [counterpart setTitle:title]; return self; } /* Icon dragging methods */ - registerWindow /* * Registers the document window with the Workspace Manager so that when the * user picks up an icon in the Workspace and drags it over our document window * and lets go, iconEntered:... and iconReleasedAt::ok: messages will be * sent to the DrawDocument from the Workspace Manager. Allows the user to * drag PostScript and TIFF files into the document. */ { unsigned int windowNum; id speaker = [NXApp appSpeaker]; listener = [[Listener allocFromZone:[self zone]] init]; [listener setDelegate:self]; [listener usePrivatePort]; [listener addPort]; NXConvertWinNumToGlobal([window windowNum], &windowNum); [speaker setSendPort:NXPortFromName(NX_WORKSPACEREQUEST, NULL)]; [speaker registerWindow:windowNum toPort:[listener listenPort]]; return self; } - unregisterWindow /* * Undoes what registerWindow does. */ { unsigned int windowNum; id speaker = [NXApp appSpeaker]; if (listener) { [speaker setSendPort:NXPortFromName(NX_WORKSPACEREQUEST, NULL)]; NXConvertWinNumToGlobal([window windowNum], &windowNum); [speaker unregisterWindow:windowNum]; [listener free]; } return self; } - (int)iconEntered:(int)windowNum at:(double)x :(double)y iconWindow:(int)iconWindowNum iconX:(double)iconX iconY:(double)iconY iconWidth:(double)iconWidth iconHeight:(double)iconHeight pathList:(char *)pathList /* * Called whenever an icon is dragged from the Workspace over the document * window. At this point, all that is done is to salt away the list of files * represented by the icon. All the real work is done in iconReleasedAt::ok:. */ { if (!iconPathList || strcmp(iconPathList, pathList)) { NX_FREE(iconPathList); iconPathList = NXCopyStringBufferFromZone(pathList, [self zone]); } return 0; } - (int)iconReleasedAt:(double)x :(double)y ok:(int *)flag /* * Goes through the list of files associated with the icon dragged * from the Workspace and checks if any of them are PostScript or TIFF. * If any are, then the GraphicView is asked to load those in as objects. * Very important: an NX_DURING handler is required around all the processing * of this method since an uncaught raised error will cause this method not * to return and thus hang the Workspace Manager for a while. */ { id inspectTuple; NXPoint p; volatile int foundOne = NO; char *file, *tab, *extension; BOOL refFlag; int irc; p.x = x; p.y = y; [window convertScreenToBase:&p]; [view convertPoint:&p fromView:nil]; NX_DURING file = iconPathList; while (file) { tab = strchr(file, '\t'); if (tab) *tab = '\0'; extension = strrchr(file, '.'); if (extension) { if (!strcmp(extension, ".ps") || !strcmp(extension, ".eps") || !strcmp(extension,".tiff") || !strcmp(extension,".tif")) { [self loadImageFile:file at:&p allowAlpha:(strcmp(extension,".tiff") && strcmp(extension,".tif"))]; foundOne = YES; } if ( !strcmp(extension, ".hippo") ) { if ( hDraw == nil ) { hDraw = NXGetNamedObject("HDrawInstance", NXApp); } inspectTuple = [hDraw inspectTuple]; irc = NXRunAlertPanel( "Open", "Import tuple by reference?", "Yes", "No", NULL); refFlag = (irc == NX_ALERTDEFAULT) ? YES : NO ; irc = [inspectTuple openTupleFile:file by:refFlag]; if ( irc == HD_REPLACE ) { [inspectTuple bindDisplays]; } if ( irc != HD_CANCEL ) { foundOne = YES; } } } file = tab ? ++tab : NULL; } if (foundOne) { [NXApp activateSelf:YES]; [window makeKeyAndOrderFront:self]; } NX_HANDLER NX_ENDHANDLER *flag = foundOne; return 0; } /* Validates whether a menu command makes sense now */ - (BOOL)validateCommand:menuCell /* * Validates whether a menu command that DrawDocument responds to * is valid at the current time. */ { SEL action = [menuCell action]; if (action == @selector(save:)) { return [view isDirty]; } else if (action == @selector(revertToSaved:)) { return ([view isDirty] && haveSavedDocument); } else if (action == @selector(saveAs:)) { return (haveSavedDocument || ![view isEmpty]); } else if (action == @selector(saveTo:)) { return ![view isEmpty]; } else if (action == @selector(close:)) { return YES; } else if (action == @selector(hideRuler:)) { if ([[window contentView] eitherRulerIsVisible] && strcmp([menuCell title], "Hide Ruler")) { [menuCell setTitle:"Hide Ruler"]; [menuCell setEnabled:NO]; } else if (![[window contentView] eitherRulerIsVisible] && strcmp([menuCell title], "Show Ruler")) { [menuCell setTitle:"Show Ruler"]; [menuCell setEnabled:NO]; } } else if (action == @selector(alignSelLeft:) || action == @selector(alignSelRight:) || action == @selector(alignSelCenter:) || action == @selector(checkSpelling:) || action == @selector(showGuessPanel:)) { return [[window getFieldEditor:NO for:NXApp] superview] ? YES : NO; } return YES; } /* Cursor-setting method */ - resetCursor /* * Sets the document's cursor according to whatever the current graphic is. * Makes the graphic view the first responder if there isn't one or if * no tool is selected (the cursor is the normal one). */ { id fr, cursor; id scrollview = [window contentView]; if ( hDraw == nil ) { hDraw = NXGetNamedObject("HDrawInstance", NXApp); } cursor = [hDraw cursor]; [scrollview setDocCursor:cursor]; fr = [window firstResponder]; if (!fr || fr == window || cursor == NXArrow) { [window makeFirstResponder:view]; } return self; } @end